/**
 * 
 */
package com.ms.service.module.baccarat;

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.gson.Gson;
import com.ms.game.common.GameType;
import com.ms.game.common.container.BasicSettingUtil;
import com.ms.game.gmtool.ISlotGameServerProcesserToGMTool;
import com.ms.game.slot.base.GainRateSystem;
import com.ms.game.slot.base.ISlotGameServerProcesser;
import com.ms.game.slot.base.message.SDMSeatStatChangeRequest;
import com.ms.game.slot.base.message.SDMVerifyExpriedTableUserRequest;
import com.ms.game.slot.base.message.SGmtoolCleanGameSeatStatusRequest;
import com.ms.game.slot.base.message.SSlotCommonDmSeatPlayerInfoRequest;
import com.ms.game.slot.base.message.STableGameAsynDoSeatDataRequest;
import com.ms.game.slot.base.message.SlotBasicGameMessageRequest;
import com.ms.game.slot.base.message.SlotGameDoSeatVerifyRequest;
import com.ms.game.slot.base.message.SlotGameLeaveBackRequest;
import com.ms.game.slot.base.message.SlotGameLeaveRequest;
import com.ms.game.slot.base.message.SlotGameLeaveResponse;
import com.ms.game.slot.base.table.DMBasicTableUser;
import com.ms.player.Player;
import com.ms.player.PlayerUserType;
import com.ms.service.error.ErrorCode;
import com.ms.service.module.account.message.SSessionCloseNotify;
import com.ms.service.module.analysis.AnalysisProcessor;
import com.ms.service.module.analysis.PlayerEnterSeatStatusType;
import com.ms.service.module.baccarat.bin.BacrBetType;
import com.ms.service.module.baccarat.bin.BacrMachineSetting;
import com.ms.service.module.baccarat.bin.BacrMachineSettingUtil;
import com.ms.service.module.baccarat.bin.BacrTableSettingInfo;
import com.ms.service.module.baccarat.db.BacrActivityCombosInfoModel;
import com.ms.service.module.baccarat.db.BacrActivityCombosLogModel;
import com.ms.service.module.baccarat.db.BacrDAO;
import com.ms.service.module.baccarat.db.BacrDeletePlayerLogFromRedisAsync;
import com.ms.service.module.baccarat.db.BacrGameLogModel;
import com.ms.service.module.baccarat.db.BacrMachineDataModel;
import com.ms.service.module.baccarat.db.BacrPlayerLogModel;
import com.ms.service.module.baccarat.db.BacrPlayerLogSaveEvent;
import com.ms.service.module.baccarat.db.BacrSaveMachineModelAsync;
import com.ms.service.module.baccarat.db.BacrSaveNewCombosTempToRedisAsync;
import com.ms.service.module.baccarat.db.BacrSavePlayerLogToRedisAsync;
import com.ms.service.module.baccarat.db.BacrSendScoreToPlayerCentralAsync;
import com.ms.service.module.baccarat.message.CBacrPlayerBetRequest;
import com.ms.service.module.baccarat.message.SBacrActivityCombosData;
import com.ms.service.module.baccarat.message.SBacrDoSeatVerify;
import com.ms.service.module.baccarat.message.SBacrDoormanAsyncHistoryData;
import com.ms.service.module.baccarat.message.SBacrPlayerBetResult;
import com.ms.service.module.gamelog.db.GameLogDAO;
import com.ms.service.module.zone.BasicZone;
import com.ms.service.module.zone.BlockType;
import com.ms.service.module.zone.message.SGameJackPotLockResponse;
import com.xpec.c4.db.model.log.basic.BasicLogModel;
import com.xpec.c4.game.common.Helper;
import com.xpec.c4.game.datamanager.BaseDataInfo;
import com.xpec.c4.game.datamanager.agent.money.ChangeCoinLister;
import com.xpec.c4.game.datamanager.agent.money.CoinType;
import com.xpec.c4.game.datamanager.agent.money.MoneyChangeParam;
import com.xpec.c4.game.datamanager.agent.money.MoneyChangeWay;
import com.xpec.c4.game.time.TimeConstants;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.game.utility.GUIDFactory;
import com.xpec.c4.game.utility.GUIDType;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.message.ConstantCode;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.message.MessageAction;
import com.xpec.c4.service.module.ConsistentGroupName;
import com.xpec.c4.service.module.ModuleName;
import com.xpec.c4.service.module.task.TaskTimer;

/**
 * 百家樂功能類別
 * 
 * @author TW15010
 */
public class BacrProcessor extends BasicZone implements ISlotGameServerProcesser, ISlotGameServerProcesserToGMTool {
	/**
	 * 認證過期時間
	 */
	public static final int VERIFY_EXPRIED_TIME = 20;

	/**
	 * 寫 game log 前等待的時間
	 */
	public static final long SAVE_GAME_LOG_WAIT_TIME = 5 * TimeConstants.MILLI_SECOND_PRE_SECOND;

	/** log4j object */
	private static final Log log = LogFactory.getLog(BacrProcessor.class);

	/**
	 * 玩家 id 對應遊戲核心的 map<br>
	 * key: player id<br>
	 * value: game core
	 */
	private HashMap<GUID, BacrGameCore> playerIdToCoreMap = new HashMap<GUID, BacrGameCore>();

	/**
	 * 遊戲核心管理 map<br>
	 * key: tableId<br>
	 * value: game core
	 */
	private HashMap<String, BacrGameCore> gameCoreMap = new HashMap<String, BacrGameCore>();

	/**
	 * 入座驗證
	 */
	private HashMap<String, STableGameAsynDoSeatDataRequest> verifyCore = new HashMap<String, STableGameAsynDoSeatDataRequest>();

	@Override
	public void onPlayerLogin(Player player) {
		super.onPlayerLogin(player);
	}

	@Override
	public void onPlayerDisconnect(SSessionCloseNotify notify) {
		super.onPlayerDisconnect(notify);

		// 離桌
		if (playerIdToCoreMap.containsKey(notify.getPlayerId())) {
			BacrGameCore core = playerIdToCoreMap.get(notify.getPlayerId());
			if (core != null) {
				playerLeave(notify.getPlayerId(), core.getTableId());
			}
		}
	}

	private int playerEnter(Player player, String tableId) {
		if (player == null) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "player = null");
			}
			return ErrorCode.INVALID_PARAM;
		}

		if (!GameUtility.isHaveString(tableId)) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "tableId = null");
			}
			return ErrorCode.INVALID_PARAM;
		}

		// 取得指定要進入的牌桌核心
		BacrGameCore core = gameCoreMap.get(tableId);
		if (core == null) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "table " + tableId + " not found in gameCoreMap.");
			}
			return ErrorCode.INVALID_HALL_TYPE;
		}

		putBlockList(player.getGuid(), null, BlockType.BACR_GET_SCORE_WAIT_DC);

		// 入桌時如果有在 blocklist 中就先清掉, 避免卡一次就永遠無法進行遊戲
		if (isBlock(player.getGuid(), null)) {
			if (log.isErrorEnabled()) {
				int blockType = getBlockType(player.getGuid(), null);
				String blockTypeStr = ConstantCode.getName(BlockType.class, blockType);
				log.error(GameUtility.getCurMethodName() + ": remove player " + player.toString() + " block type "
						+ blockTypeStr + " after enter seat.");
			}
			removeBlockList(player.getGuid(), null);
		}

		core.addPlayer(player);
		playerIdToCoreMap.put(player.getGuid(), core);

		// 玩家出入座記錄
		AnalysisProcessor.sendPlayerEnterSeat(player.getNameWithNumber(), player.getGuid().toString(),
				player.getLastLoginPlatform(), GameType.BACR, tableId, 0, GainRateSystem.EXCEPT_GAME_GAIN_RATE_ID,
				PlayerEnterSeatStatusType.ENTER_SEAT_SUCCESS, player.getUserType(), player.isTrialAccount(),
				player.isUseTrialCoin());

		return ErrorCode.SUCCESS;
	}

	private int playerLeave(GUID playerId, String tableId) {
		if (playerId == null) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "playerId = null");
			}
			return ErrorCode.INVALID_PARAM;
		}

		if (!GameUtility.isHaveString(tableId)) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "tableId = null");
			}
			return ErrorCode.INVALID_PARAM;
		}

		// 取得玩家所在的牌桌核心
		BacrGameCore core = gameCoreMap.get(tableId);
		if (core == null) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "table " + tableId + " not found in gameCoreMap");
			}
			return ErrorCode.INVALID_HALL_TYPE;
		}

		if (!core.isPlayerInTable(playerId)) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "player " + playerId + " is not in table " + core.getTableId());
			}
			return ErrorCode.BACR_PLAYER_NOT_IN_SPECIFY_TABLE;
		}

		Player player = core.getPlayerInTable(playerId);

		core.playerLeave(playerId);
		playerIdToCoreMap.remove(playerId);

		// 玩家出入座記錄
		AnalysisProcessor.sendPlayerEnterSeat(player.getNameWithNumber(), player.getGuid().toString(),
				player.getLastLoginPlatform(), GameType.BACR, tableId, 0, GainRateSystem.EXCEPT_GAME_GAIN_RATE_ID,
				PlayerEnterSeatStatusType.LEAVE_SEAT, player.getUserType(), player.isTrialAccount(),
				player.isUseTrialCoin());

		return ErrorCode.SUCCESS;
	}

	/**
	 * 主要更新事件
	 */
	public void onUpdate() {
		// 是否在等待關服狀態
		boolean waitToClose = getOwnerModule().isWaitToClose();

		for (Entry<String, BacrGameCore> entry : gameCoreMap.entrySet()) {
			BacrGameCore core = entry.getValue();

			// 切換到等待關服狀態
			if (waitToClose && !core.isServerWaitToClose()) {
				core.setServerWaitToClose(true);
			}

			// 更新牌桌核心
			boolean statusChange = core.update();

			BacrGameStatus nextStatus = core.getNextStatus();
			BacrGameStatus curStatus = core.getCurStatus();
			if (nextStatus != null && curStatus != null && nextStatus != curStatus) {
				// 某階段結束時
				switch (curStatus) {
				case CALCULATE_SCORE: {
					onCalculateScoreStepEnd(core);
					break;
				}
				case WAIT_TO_START: {
					fixCombosInfo(core);
					break;
				}

				default:
					break;
				}
			}

			// 進入結算分數階段
			if (statusChange) {
				// 某階段開始時
				switch (core.getCurStatus()) {
				case SHUFFLE_CARD: {
					sendDoormanAsyncHistoryDataToDm(core.getTableId(), core.getHandCount(), true, null);
					break;
				}
				case PLAYER_BET: {
					core.robotBetProc();
					break;
				}
				case CALCULATE_SCORE: {
					try {
						onCalculateScoreStep(core);
						onCalculateCombosStep(core);
					} catch (Exception e) {
						if (log.isErrorEnabled()) {
							log.error("CALCULATE_SCORE fail", e);
						}
					}
					break;
				}
				case DEALT: {
					// 先等待一段時間，確保到 dc 的 query 有回來才寫 game log
					BacrSaveGameDataTimerTask saveLogTask = new BacrSaveGameDataTimerTask();
					saveLogTask.setProc(this);
					saveLogTask.setCore(core);
					getOwnerModule().addTaskTimer(saveLogTask, TaskTimer.ONCE, SAVE_GAME_LOG_WAIT_TIME, 0, null);
					break;
				}
				// 關機狀態(收到GMTOOL關機時只會進入一次)
				case CLOSE: {
					saveCombosInfoForReboot(core);
				}

				default:
					break;
				}
			}
		} // for (Entry<String, BacrGameCore> entry : gameCoreMap.entrySet()) {
	}

	/**
	 * 結算分數階段結束時
	 * 
	 * @param core
	 */
	public void onCalculateScoreStepEnd(BacrGameCore core) {
		// 寫入還沒處理過得的玩家 log
		HashMap<GUID, BacrBetPlayerInfo> betPlayerInfoMap = core.getPlayerBetStep().getBetPlayerInfoMap();
		HashMap<GUID, BacrPlayerGetScoreInfo> playerScoreList = core.getCalculateScoreStep().getPlayerScoreList();

		// 掃一遍所有有押注的玩家資訊
		for (Entry<GUID, BacrBetPlayerInfo> entry : betPlayerInfoMap.entrySet()) {
			BacrBetPlayerInfo playerInfo = entry.getValue();
			BacrPlayerGetScoreInfo scoreInfo = playerScoreList.get(playerInfo.getPlayerId());

			// 有處理過得就跳過
			if (playerInfo.isHaveLogProc()) {
				continue;
			}

			// 寫 player log 到 db
			BacrPlayerLogModel logModel = playerInfo.getLogModel();

			BigDecimal totalScore = scoreInfo.getTotalScore();
			if (Helper.isEqual(totalScore, BigDecimal.ZERO)) { // 沒有得分的
				logModel.setSaveOnEvent(BacrPlayerLogSaveEvent.CALCULATE_SCORE);
			} else { // 有得分的
				logModel.setSaveOnEvent(BacrPlayerLogSaveEvent.CALCULATE_SCORE_TIMEOUT);
			}

			getOwnerModule().saveDbLog(logModel);

			// 將 redis 中的 log 刪除
			BacrDeletePlayerLogFromRedisAsync delLogTask = new BacrDeletePlayerLogFromRedisAsync(
					core.getTableId(), core.getRoundNumber(), playerInfo.getPlayerId().toString());
			getOwnerModule().addAsyncCommand(delLogTask);

			// 標記為處理過
			playerInfo.setHaveLogProc(true);
		}
	}

	/**
	 * 寫 game log 的時間到了
	 * 
	 * @param core
	 */
	public void onTimerTriggerSaveGameData(BacrGameCore core) {
		core.updateMachineDataAndLog();

		// 寫 log
		BacrGameLogModel model = core.getGameLogModel();
		getOwnerModule().saveDbLog(model);

		// 更新機台資料到 db
		BacrSaveMachineModelAsync task = new BacrSaveMachineModelAsync();
		BacrMachineDataModel machineModel = null;
		try {
			machineModel = core.getMachineModel().clone();
		} catch (CloneNotSupportedException e) {
			if (log.isErrorEnabled()) {
				log.error("clone " + core.getTableId() + " machine model fail.", e);
			}
		}
		task.setModel(machineModel);
		getOwnerModule().addAsyncCommand(task);
	}

	/**
	 * 分數結算完畢，開始分錢給有得分的玩家
	 */
	private void onCalculateScoreStep(BacrGameCore core) {
		// 更新牌桌資訊給 dm
		sendDoormanAsyncHistoryDataToDm(core.getTableId(), core.getHandCount(), false, core.getCurrentGameResult());

		// 分錢給有得分的玩家
		if (core.getReturnScoreProcQueue().size() <= 0) {
			return;
		}

		// 一開始先處理一部分
		updateScoreMoneyToPlayer(core, 10);
	}

	/**
	 * 處理連勝相關事宜(活動連勝、排行榜連勝)
	 */
	private void onCalculateCombosStep(BacrGameCore core) {
		// 送更新排行榜
		sendUpdateBacrRankComboToRankingModule(core);

		// 送活動連勝資訊給CLIENT
		sendUpdateBacrActivityCombosToClient(core);

	}

	/**
	 * 更新牌桌資訊給 dm
	 * 
	 * @param tableId
	 *            牌桌 id
	 * @param handCount
	 *            第幾鋪牌
	 * @param resetResultList
	 *            是否要重置牌桌的歷史資訊
	 * @param result
	 *            牌局勝負結果
	 */
	private void sendDoormanAsyncHistoryDataToDm(String tableId, int handCount, boolean resetResultList,
			BacrGameResult result) {
		// 更新牌桌資訊給 dm
		SBacrDoormanAsyncHistoryData msg = new SBacrDoormanAsyncHistoryData();
		msg.setTableId(tableId);
		msg.setHandCount(handCount);
		msg.setResetResultList(resetResultList);
		msg.setResult(result);

		sendGroupMessage(MessageTypes.S_BACR_DOORMAN_ASYNC_HISTORYDATA, msg, ModuleName.BACR_DOORMAN,
				getOwnerModule().getModuleName());
	}

	/**
	 * 依照玩家類型判斷應該使用哪種幣進行遊戲
	 * 
	 * @param core
	 * @param player
	 * @return
	 */
	private CoinType getCoinTypeForBet(BacrGameCore core, Player player) {
		CoinType coinType = null;
		if (!player.isUseTrialCoin()) {
			// 玩家使用正式幣
			coinType = CoinType.GOLD;

			// 體驗桌 && 玩家是機器人
			if (core.getMachineSetting().isTrial() && player.getUserType() == PlayerUserType.ROBORT) {
				// 改用體驗幣
				coinType = CoinType.TRIAL;
			}
		} else {
			// 玩家使用體驗幣
			coinType = CoinType.TRIAL;
		}

		return coinType;
	}

	/**
	 * 將得分的錢更新到玩家資料裡
	 * 
	 * @param processCount
	 *            一次處理的數量
	 */
	private void updateScoreMoneyToPlayer(BacrGameCore core, int processCount) {
		// 不是結算階段就不處理
		if (core.getCurStatus() != BacrGameStatus.CALCULATE_SCORE) {
			return;
		}

		Queue<BacrPlayerGetScoreInfo> returnScoreProcQueue = core.getReturnScoreProcQueue();
		if (returnScoreProcQueue.size() <= 0) {
			return;
		}

		for (int i = 0; i < processCount; i++) {
			// 已經處理完了就直接結束
			if (returnScoreProcQueue.size() <= 0) {
				break;
			}

			BacrPlayerGetScoreInfo scoreInfo = returnScoreProcQueue.poll();

			GUID playerId = scoreInfo.getPlayerId();

			// 有押注的玩家基本資料
			BacrBetPlayerInfo betPlayerInfo = core.getBetPlayerInfo(playerId);

			if (log.isInfoEnabled()) {
				log.info(BacrSendScoreToPlayerCentralAsync.LOG_TRACE_HEADER + core.getTableId()
						+ " poll send score task from queue, left:" + returnScoreProcQueue.size() + " player:"
						+ scoreInfo.getPlayerId() + " orderId:" + betPlayerInfo.getOrderId());
			}

			// 得分
			BigDecimal score = scoreInfo.getTotalScore();

			// 是否曾經離開過
			boolean haveLeave = core.isPlayerHaveLeave(playerId);

			if (isBlock(playerId, null)) {
				if (log.isErrorEnabled()) {
					log.error(BacrSendScoreToPlayerCentralAsync.LOG_TRACE_HEADER + GameUtility.getCurMethodName()
							+ ": " + core.getTableId() + " player " + playerId + " still in wait status "
							+ ConstantCode.getName(BlockType.class, getBlockType(playerId, null)) + " skip.");
				}
				continue;
			}

			Player player = getPlayer(playerId);

			if (player != null) {
				if (player.isDcQuery()) {
					if (log.isErrorEnabled()) {
						log.error(BacrSendScoreToPlayerCentralAsync.LOG_TRACE_HEADER + core.getTableId()
								+ " player " + playerId + " isDcQuery() = true, skip.");
					}
					continue;
				}
			}

			// 幣別
			CoinType coinType = betPlayerInfo.getCoinType();

			// 指定幣別目前的數值
			BigDecimal currentValue = BigDecimal.ZERO;
			if (player != null) {
				currentValue = player.getCoin(coinType);
			}

			// 曾經離開過牌桌的就送保險箱
			boolean scoreToBank = false;
			if (!core.getMachineSetting().isTrial()) { // 正式桌才有送保險箱
				// 取不到玩家資料或押注後曾經離開牌局
				if (player == null || haveLeave) {
					// 送保險箱
					scoreToBank = true;
				}
			}

			// 活動連勝得分
			int activityComboScore = 0;
			BacrPlayerActivityCombosInfo comboInfo = core.getActivityCombosScoreInfos().get(playerId);
			if (comboInfo != null) {
				if (comboInfo.getActivityCombosScore() > 0) {
					activityComboScore = (int) comboInfo.getActivityCombosScore();
				}
			}

			// 丟到 dc 一次處理
			BacrSendScoreToPlayerCentralAsync task = new BacrSendScoreToPlayerCentralAsync();
			task.setParameters(core.getTableId(), getOwnerModule().getModuleName(), betPlayerInfo, coinType,
					currentValue, score.intValue(), scoreToBank, activityComboScore);
			task.key = betPlayerInfo.getPlayerId();
			getOwnerModule().addCentralAsyncCommandToDC(task, this, core, playerId);

			if (player != null) {
				player.setDcQuery(true);

				// 更新等待處理狀態
				putBlockList(player.getGuid(), null, BlockType.BACR_GET_SCORE_WAIT_DC);
			}

			// player log 資料還沒被處理過
			if (!betPlayerInfo.isHaveLogProc()) {
				// 寫 player log 到 db
				BacrPlayerLogModel logModel = betPlayerInfo.getLogModel();
				logModel.setSendToBank(scoreToBank);
				logModel.setSaveOnEvent(BacrPlayerLogSaveEvent.CALCULATE_SCORE);
				getOwnerModule().saveDbLog(logModel);

				// 將 redis 中的 log 刪除
				BacrDeletePlayerLogFromRedisAsync delLogTask = new BacrDeletePlayerLogFromRedisAsync(
						core.getTableId(), core.getRoundNumber(), betPlayerInfo.getPlayerId().toString());
				getOwnerModule().addAsyncCommand(delLogTask);

				// 標記為處理過
				betPlayerInfo.setHaveLogProc(true);

				// 送到任務module
				try {
					sendBACRMissionCheck(betPlayerInfo, scoreInfo);
				} catch (Exception e) {
					if (log.isErrorEnabled()) {
						log.error("updateScoreMoneyToPlayer try send mission check fail", e);
					}
				}

			}
		} // for (int i = 0; i < processCount; i++) {
	}

	/**
	 * 到 DC 將分數更新到玩家身上的 callback
	 * 
	 * @param core
	 * @param playerId
	 */
	public void onSendScoreToPlayerCallback(BacrGameCore core, GUID playerId) {
		// 解除等待處理狀態
		if (playerId != null) {
			Player player = getPlayer(playerId);
			if (player != null) {
				player.setDcQuery(false);
			}

			removeBlockList(playerId, null);
		}

		// 每完成一個就再處理一個
		updateScoreMoneyToPlayer(core, 1);
	}

	/**
	 * 送給任務檢查(是否是認證角色只能交給任務module自己判斷了)
	 */
	public void sendBACRMissionCheck(BacrBetPlayerInfo betPlayerInfo, BacrPlayerGetScoreInfo scoreInfo) {
		if (betPlayerInfo == null || scoreInfo == null) {
			return;
		}
		if (betPlayerInfo.getUserType() != PlayerUserType.PLAYER) {
			return;
		}
		if (betPlayerInfo.getCoinType() == null || betPlayerInfo.getCoinType().equals(CoinType.TRIAL)) {
			return;
		}

		GUID accountID = betPlayerInfo.getAccountId();
		GUID playerGUID = betPlayerInfo.getPlayerId();
		String gameType = GameType.BACR.name();
		try {
			// 從LOG裡面取得下注金額
			BacrPlayerLogModel model = betPlayerInfo.getLogModel();
			long totalBet = model.getBet();
			long totalScore = model.getScore();
			long totalGain = totalScore - totalBet;
			String totalBetStr = String.valueOf(totalBet);
			String hallType = model.getTableId();
			String totalGainStr = String.valueOf(totalGain);

			BigDecimal bankerPair = scoreInfo.getBankerPair();
			BigDecimal playerPair = scoreInfo.getPlayerPair();
			BigDecimal tie = scoreInfo.getTie();

			// 所以玩家身上如果同時有這三個任務會一次全部完成
			// 押中莊對
			if (Helper.isGreater(bankerPair, BigDecimal.ZERO)) {
			}

			// 押中閒對
			if (Helper.isGreater(playerPair, BigDecimal.ZERO)) {
			}

			// 押中和
			if (Helper.isGreater(tie, BigDecimal.ZERO)) {
			}
		} catch (Exception e) {
			if (log.isErrorEnabled()) {
				log.error("sendBACRMissionCheck fail, accountID:" + accountID.toString() + ", playerGUID:"
						+ playerGUID.toString(), e);
			}
			return;
		}
	}

	/**
	 * 玩家押注要求
	 * 
	 * @param request
	 */
	public void recvPlayerBetRequest(CBacrPlayerBetRequest request) {
		if (request == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": request = null.");
			}
			return;
		}

		Player player = baseaction.getPlayer();
		if (player == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": get player fail.");
			}
			return;
		}

		// 處理狀態檢查
		if (isBlock(player.getGuid(), null)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": procStatus="
						+ ConstantCode.getName(BlockType.class, getBlockType(player.getGuid(), null))
						+ ", can't bet.");
			}
			MessageAction.sendFailResultClientMessage(player.getSessionID(),
					MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
					ErrorCode.BACR_WAIT_BLOCK_CANT_BET_NOW);
			return;
		}

		// 取得玩家所在的牌桌核心
		BacrGameCore core = playerIdToCoreMap.get(player.getGuid());
		if (core == null) {
			MessageAction.sendFailResultClientMessage(player.getSessionID(),
					MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
					ErrorCode.BACR_PLAYER_NOT_IN_TABLE);
			return;
		}

		// 牌桌已經關閉 (server 等待關服)
		if (core.getCurStatus() == BacrGameStatus.CLOSE) {
			MessageAction.sendFailResultClientMessage(player.getSessionID(),
					MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
					ErrorCode.GAME_SERVER_WAIT_TO_CLOSE);
			return;
		}

		// 牌桌設定
		BacrTableSettingInfo mSetting = core.getMachineSetting();

		// 目前階段不能押注
		BacrGameStatus gameStatus = core.getCurStatus();
		if (gameStatus != BacrGameStatus.PLAYER_BET) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": player " + player + " can't bet, current step is "
						+ gameStatus.name());
			}
			MessageAction
					.sendFailResultClientMessage(player.getSessionID(), MessageTypes.S_BACR_PLAYER_BET_RESULT,
							SBacrPlayerBetResult.class, ErrorCode.BACR_NOT_BET_STEP);
			return;
		}

		// 正式桌的其他規定
		if (!mSetting.isTrial()) {
			// 玩家才要限制的下注規定
			if (player.getUserType() == PlayerUserType.PLAYER) {

				// // 等級限制
				// int limitLevel = mSetting.getLimitLevel();

				// 等級不足不能下注
				if (player.getLevel() < 50) {
					MessageAction.sendFailResultClientMessage(player.getSessionID(),
							MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
							ErrorCode.BACR_BET_LEVEL_NOT_ENOUGH);
					return;
				}

				// 必須要是認證會員
				if (!player.isVerifiedMember()) {
					MessageAction.sendFailResultClientMessage(player.getSessionID(),
							MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
							ErrorCode.BACR_BET_NOT_VERIFY_MEMBER);
					return;
				}

				// 必須要有設定銀行密碼
				if (!player.isHaveBankPassword()) {
					MessageAction.sendFailResultClientMessage(player.getSessionID(),
							MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
							ErrorCode.BACR_BET_NO_BANK_PASSWORD);
					return;
				}
			} // if (player.getUserType() == PlayerUserType.PLAYER) {
		} // if (!mSetting.isTrial()) {

		// 重複押注的檢查
		if (core.isPlayerBet(player.getGuid())) {
			MessageAction.sendFailResultClientMessage(player.getSessionID(),
					MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
					ErrorCode.BACR_CANT_DUPLICATE_BET);
			return;
		}

		// 押注資訊沒資料
		if (GameUtility.getArrayListSize(request.getBetInfoList()) == 0) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": BetInfoList size = 0.");
			}
			MessageAction
					.sendFailResultClientMessage(player.getSessionID(), MessageTypes.S_BACR_PLAYER_BET_RESULT,
							SBacrPlayerBetResult.class, ErrorCode.INVALID_BET_MONEY);
			return;
		}

		// 計算總押注金額
		BigDecimal totalBetMoney = BigDecimal.ZERO;

		// 檢查押注類型是否有重複
		HashSet<Integer> betTypeSet = new HashSet<Integer>();

		for (BacrBetInfo betInfo : request.getBetInfoList()) {
			// 不正確的押注類型
			if (!ConstantCode.isValid(BacrBetType.class, betInfo.getBetType())) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + ": invalid bet type " + betInfo.getBetType());
				}
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.INVALID_BET_MONEY);
				return;
			} // is bet type not valid

			// 不正確的押注金額
			if (betInfo.getBetValue() <= 0) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + ": invalid bet value " + betInfo.getBetValue()
							+ "in type " + ConstantCode.getName(BacrBetType.class, betInfo.getBetType()));
				}
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.INVALID_BET_MONEY);
				return;
			} // is bet value <= 0

			// 押注金額低於單部位最小押注額
			if (betInfo.getBetValue() < mSetting.getMinimizeBetValueInOnePart()) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + ": bet value " + betInfo.getBetValue()
							+ " less than low limit " + mSetting.getMinimizeBetValueInOnePart() + " in type "
							+ ConstantCode.getName(BacrBetType.class, betInfo.getBetType()));
				}
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.BACR_BET_VALUE_LESS_THAN_ONE_PART_LOW_LIMIT);
				return;
			}

			// 必須要能被 100 整除
			int modValue = betInfo.getBetValue() % 100;
			if (modValue != 0) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + ": bet value " + betInfo.getBetValue()
							+ " % 100 = " + modValue + " can not div 100 in type "
							+ ConstantCode.getName(BacrBetType.class, betInfo.getBetType()));
				}
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.INVALID_BET_MONEY);
				return;
			}

			// 押注金額大於部位限制值
			int highLimit = 0;
			switch (betInfo.getBetType()) {
			case BacrBetType.PLAYER:
				highLimit = mSetting.getMaxPlayerBetMoney();
				break;

			case BacrBetType.PLAYER_PAIR:
				highLimit = mSetting.getMaxPlayerPairBetMoney();
				break;

			case BacrBetType.BANKER_PAIR:
				highLimit = mSetting.getMaxBankerPairBetMoney();
				break;

			case BacrBetType.BANKER:
				highLimit = mSetting.getMaxBankerBetMoney();
				break;

			case BacrBetType.TIE:
				highLimit = mSetting.getMaxTieBetMoney();
				break;
			}

			if (betInfo.getBetValue() > highLimit) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + ": bet value " + betInfo.getBetValue()
							+ " great than high limit " + highLimit + " in type "
							+ ConstantCode.getName(BacrBetType.class, betInfo.getBetType()));
				}
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.BACR_BET_VALUE_GREAT_THAN_HIGH_LIMIT);
				return;
			}

			// 押注類型重複
			if (betTypeSet.contains((int) betInfo.getBetType())) {
				if (log.isErrorEnabled()) {
					log.error(GameUtility.getCurMethodName() + ": duplicate bet type "
							+ ConstantCode.getName(BacrBetType.class, betInfo.getBetType()));
				}
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.BACR_DUPLICATE_BET_TYPE);
				return;
			} // is have duplicate bet type

			totalBetMoney = Helper.add(totalBetMoney, new BigDecimal(betInfo.getBetValue()));

			betTypeSet.add((int) betInfo.getBetType());
		} // for (BacrBetInfo betInfo : request.getBetInfoList()) {

		// 玩家使用的幣別
		CoinType coinType = getCoinTypeForBet(core, player);

		// 玩家的錢
		BigDecimal playerMoney = BigDecimal.ZERO;
		if (coinType == CoinType.GOLD) {
			playerMoney = player.getGoldCoin();
		} else {
			playerMoney = player.getTrialCoin();
		}

		// 檢查使用的幣別是否正確

		// 正式桌
		if (!core.getMachineSetting().isTrial()) {
			// 使用體驗幣
			if (coinType == CoinType.TRIAL) {
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.CAN_NOT_USE_TRIAL_COIN_TO_BET);
				return;
			}
		} else { // 體驗桌
			// 使用正式幣
			if (coinType == CoinType.GOLD) {
				MessageAction.sendFailResultClientMessage(player.getSessionID(),
						MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
						ErrorCode.CAN_NOT_USE_GOLD_COIN_TO_BET);
				return;
			}
		}

		// 玩家身上的錢不夠
		if (Helper.isLess(playerMoney, totalBetMoney)) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": money not enough, player=" + playerMoney + " bet="
						+ totalBetMoney);
			}
			MessageAction
					.sendFailResultClientMessage(player.getSessionID(), MessageTypes.S_BACR_PLAYER_BET_RESULT,
							SBacrPlayerBetResult.class, ErrorCode.INVALID_BET_MONEY);
			return;
		} // if player money < total money

		// money
		ChangeCoinLister coinLister = new ChangeCoinLister();
		coinLister.addCoin(coinType, totalBetMoney);

		// order id
		GUID orderId = GUIDFactory.createUUID(GUIDType.TYPE_BACR);

		// changeWay
		MoneyChangeWay changeWay = MoneyChangeWay.SUB_BET;

		// gameId
		String gameId = GameType.BACR.name();

		// gameName
		String gameName = BasicSettingUtil.getGameTypeNameByStringTable(GameType.BACR.name());

		// gameHallId
		String gameHallId = core.getTableId();

		// gameHallName
		String gameHallName = "";

		// classOfCallback
		Object classOfCallback = this;

		// callback
		String callbackMethod = "onPlayerBetUpdateMoneyToDcCallback";

		// 透過 dc 扣錢
		boolean res = getOwnerModule().getPlayerDataAgent().updatePlayerMoneyToDC(orderId, player, coinLister,
				changeWay, MoneyChangeParam.NO_JACKPOT, false, null, null, getOwnerModule(), gameId, gameName,
				gameHallId, gameHallName, null, classOfCallback, callbackMethod, player, request, core, coinType,
				playerMoney, core.getRoundNumber());

		if (!res) {
			MessageAction.sendFailResultClientMessage(player.getSessionID(),
					MessageTypes.S_BACR_PLAYER_BET_RESULT, SBacrPlayerBetResult.class,
					ErrorCode.VERIFICATION_FAILED);
			return;
		}

		// 更新機台作業狀態
		putBlockList(player.getGuid(), null, BlockType.BACR_PLAYER_BET_WAIT_DC_SUB_MONEY);

		// 累加押注送到 dc 處理的次數
		core.increaseBetToDcCount();
	}

	/**
	 * 押注後向 dc 扣錢回來的 callback
	 * 
	 * @param obj
	 * @param userObject
	 *            [0] Player player<br>
	 *            [1] CBacrPlayerBetRequest request<br>
	 *            [2] BacrGameCore core<br>
	 *            [3] CoinType coinType<br>
	 *            [4] BigDecimal playerBeforeMoney<br>
	 *            [5] String roundNumber
	 */
	public void onPlayerBetUpdateMoneyToDcCallback(BaseDataInfo obj, Object... userObject) {
		MoneyChangeParam param = (MoneyChangeParam) obj.getData();

		Player player = (Player) userObject[0];
		GUID sessionId = player.getSessionID();
		CBacrPlayerBetRequest request = (CBacrPlayerBetRequest) userObject[1];
		BacrGameCore core = (BacrGameCore) userObject[2];
		CoinType coinType = (CoinType) userObject[3];
		BigDecimal playerBeforeMoney = (BigDecimal) userObject[4];
		String roundNumber = (String) userObject[5];

		GUID playerId = param.getPlayerId();

		// 更新機台作業狀態
		removeBlockList(playerId, null);

		// 檢查扣押注錢執行結果
		if (param.getResult() != ErrorCode.SUCCESS) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": player " + playerId
						+ " bet, update money to db fail.");
			}
			MessageAction.sendFailResultClientMessage(sessionId, MessageTypes.S_BACR_PLAYER_BET_RESULT,
					SBacrPlayerBetResult.class, param.getResult());
			return;
		}

		// 設定押注資訊
		int res = core.setPlayerBetInfo(player, coinType, playerBeforeMoney, param.getOrderId(), roundNumber,
				request);
		if (res != ErrorCode.SUCCESS) {
			MessageAction.sendFailResultClientMessage(sessionId, MessageTypes.S_BACR_PLAYER_BET_RESULT,
					SBacrPlayerBetResult.class, res);
			return;
		}

		// 將 log 寫入 redis 暫存, 當備份
		BacrBetPlayerInfo betPlayerInfo = core.getBetPlayerInfo(playerId);
		BacrPlayerLogModel model = null;
		try {
			model = betPlayerInfo.getLogModel().clone();
		} catch (Exception e) {
			if (log.isErrorEnabled()) {
				log.error(core.getTableId() + " clone player " + player.toString() + " log fail.", e);
			}
		}

		if (model != null) {
			BacrSavePlayerLogToRedisAsync task = new BacrSavePlayerLogToRedisAsync(core.getTableId(), model);
			getOwnerModule().addAsyncCommand(task);
		}

		// 寫活動連勝LOG
		ArrayList<BacrActivityCombosLogModel> activityCombsLogmodel = core.getBacrActivityCombosLog(playerId);
		if (activityCombsLogmodel != null) {
			for (BacrActivityCombosLogModel activityCombosLog : activityCombsLogmodel) {
				if (activityCombosLog != null) {
					getOwnerModule().saveDbLog(activityCombosLog);
				}
			}
		}

		// 回傳成功訊息
		MessageAction.sendFailResultClientMessage(sessionId, MessageTypes.S_BACR_PLAYER_BET_RESULT,
				SBacrPlayerBetResult.class, ErrorCode.SUCCESS);

		// 將押注資訊廣播給其他玩家
		core.broadcastBetInfoToOtherPlayers(playerId, request);
	}

	// 取得玩家所在的牌桌核心
	public BacrGameCore getCore(GUID playerId) {
		if (playerId == null) {
			return null;
		}

		BacrGameCore core = playerIdToCoreMap.get(playerId);

		return core;
	}

	/**
	 * 啟動要再這個 module 執行的遊戲核心
	 */
	public void initialGameCore() {
		for (BacrTableSettingInfo info : BacrMachineSetting.getInstance().getTableList()) {
			// 牌桌 id
			String tableId = info.getTableId();

			// 執行這個遊戲核心的 module
			String executeModule = ConsistentGroupName.lookUpByString(ModuleName.GROUP_BACR, tableId);

			// 目前的 module
			String curModule = getOwnerModule().getModuleName();

			// 目前的 module 是執行此核心的 module 就啟動他
			if (curModule.equals(executeModule)) {
				BacrMachineDataModel model = BacrDAO.loadMachineModel(tableId);

				// 初始化核心
				BacrGameCore core = new BacrGameCore();
				core.initialFromModel(model);
				core.setGameModule((BacrGameModule) getOwnerModule());

				// 加入管理核心
				gameCoreMap.put(tableId, core);

				// 將 redis 中暫存的 player log 處理完
				String keyPattern = GameType.BACR.name() + ":" + core.getTableId() + ":*";
				ArrayList<String> keyList = GameLogDAO.getKeyListFromRedis(keyPattern);

				for (String key : keyList) {
					BasicLogModel bm = GameLogDAO.loadLogModelFromRedis(key);
					if (bm == null) {
						continue;
					}

					if (!(bm instanceof BacrPlayerLogModel)) {
						continue;
					}

					// 將 log 寫入 db
					BacrPlayerLogModel m = (BacrPlayerLogModel) bm;
					m.setSaveOnEvent(BacrPlayerLogSaveEvent.SERVER_STARTUP);
					getOwnerModule().saveDbLog(m);
				} // for (String key : keyList) {

				// 從 redis 中刪除
				GameLogDAO.deleteManyDataFromRedis(keyList);
			} // if (curModule.equals(executeModule)) {
		} // for (BacrTableSettingInfo info : ...
	}

	/**
	 * 玩家入座驗證
	 * 
	 * @param request
	 */
	@Override
	public void revDoSeatVerify(SlotGameDoSeatVerifyRequest request) {
		String halltype = request.getHalltype();
		Player player = this.baseaction.getPlayer();
		String guid = player.getGuid().toString();
		GUID sessionid = this.baseaction.getSessionID();

		SBacrDoSeatVerify response = new SBacrDoSeatVerify();
		if (verifyCore.containsKey(guid)) {
			response.setErrorCode(ErrorCode.NO_TARGET_DATA);
			sendMessageToClient(sessionid, MessageTypes.S_BACR_DOSEATVERIFY, response);
			return;
		}

		verifyCore.remove(guid);

		int error = playerEnter(player, halltype);

		response.setErrorCode(error);
		sendMessageToClient(sessionid, MessageTypes.S_BACR_DOSEATVERIFY, response);
	}

	@Override
	public void revDisConnect(SSessionCloseNotify request) {
		GUID playerId = request.getPlayerId();
		if (playerId == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": playerId=null");
			}
			return;
		}

		BacrGameCore core = playerIdToCoreMap.get(playerId);
		if (core == null) {
			return;
		}

		// 通知DM 玩家離開
		Player player = getPlayer(playerId);
		String srcID = baseaction.getModuleID();
		GUID sessionid = request.getSessionId();

		// 通知DM刪除資料
		SDMSeatStatChangeRequest dm_request = new SDMSeatStatChangeRequest();
		dm_request.setHalltype(core.getTableId());
		dm_request.setPlayer(player);
		dm_request.setSessionid(sessionid);
		dm_request.setSrcModuleID(srcID);

		sendMessageToServer(sessionid, ModuleName.BACR_DOORMAN, MessageTypes.S_SLOT_DM_SEATSTAT_CHANGE, dm_request);

		// 直接刪除資料
		onPlayerDisconnect(request);
	}

	/**
	 * 玩家離座
	 */
	public void revGameLeave(SlotGameLeaveRequest request) {
		String halltype = request.getHalltype();
		Player player = this.baseaction.getPlayer();
		String srcID = baseaction.getModuleID();
		GUID sessionid = baseaction.getSessionID();

		int error = playerLeave(player.getGuid(), halltype);
		if (error != ErrorCode.SUCCESS) {
			SlotGameLeaveResponse response = new SlotGameLeaveResponse();
			response.setErrorCode(error);
			sendMessageToClient(sessionid, MessageTypes.S_SLOT_GAMELEAVEBACK, response);
			return;
		}

		BacrTableSettingInfo setter = BacrMachineSettingUtil.getTableSetting(halltype);

		// 通知DM刪除資料
		SDMSeatStatChangeRequest dm_request = new SDMSeatStatChangeRequest();
		dm_request.setHalltype(halltype);
		dm_request.setPlayer(player);
		dm_request.setSessionid(sessionid);
		dm_request.setSrcModuleID(srcID);

		// 詢問一下dmid放哪裡
		sendMessageToServer(sessionid, ModuleName.BACR_DOORMAN, MessageTypes.S_SLOT_DM_SEATSTAT_CHANGE, dm_request);

	}

	/**
	 * dm 離座處理完回呼game server
	 * 
	 * @param request
	 */
	@Override
	public void revLeaveSeatBack(SlotGameLeaveBackRequest request) {
		GUID sessionid = request.getSessionid();
		if (sessionid == null) {
			return;
		}

		SlotGameLeaveResponse response = new SlotGameLeaveResponse();
		response.setErrorCode(request.getErrorCode());
		sendMessageToClient(sessionid, MessageTypes.S_SLOT_GAMELEAVEBACK, response);
	}

	@Override
	public void revGmtoolCleanGameSeatStatus(SGmtoolCleanGameSeatStatusRequest request) {

		String tableId = request.getHalltype();
		GUID guid = request.getPlayerId();

		// 取得指定要進入的牌桌核心
		BacrGameCore core = gameCoreMap.get(tableId);
		if (core == null) {
			if (log.isErrorEnabled()) {
				GameUtility.logErrorException(log, "table " + tableId + " not found in gameCoreMap.");
			}
			return;
		}

		core.playerLeave(guid);

		if (playerIdToCoreMap.containsKey(guid)) {
			playerIdToCoreMap.remove(guid);
		}
	}

	/**
	 * 同步
	 * 
	 * @param request
	 */
	public void revAsyncDoSeatData(STableGameAsynDoSeatDataRequest request) {
		STableGameAsynDoSeatDataRequest verify_request = (STableGameAsynDoSeatDataRequest) request;
		verify_request.setExpriedtime(TimeSys.currentTimeSec() + VERIFY_EXPRIED_TIME);
		verifyCore.put(verify_request.getPlayerGUID().toString(), verify_request);
	}

	/**
	 * 檢測驗證過期
	 */
	public void checkExpriedVerifyData() {
		Collection<STableGameAsynDoSeatDataRequest> list = this.verifyCore.values();

		if (list.size() == 0) {
			return;
		}

		long nowtime = TimeSys.currentTimeSec();
		ArrayList<DMBasicTableUser> expriedlist = new ArrayList<DMBasicTableUser>();
		for (STableGameAsynDoSeatDataRequest data : list) {
			if (data.getExpriedtime() <= nowtime) {
				expriedlist.add(data.getUser());
			}
		}

		SDMVerifyExpriedTableUserRequest response = new SDMVerifyExpriedTableUserRequest();
		response.setUserdata(expriedlist);

		this.sendMessageToServer(null, ModuleName.BACR_DOORMAN, MessageTypes.S_SLOT_VERIFYEXPRIED, response);
	}

	public void recvStartGameNotify(long waitTimeDuration) {
		for (Entry<String, BacrGameCore> entry : gameCoreMap.entrySet()) {
			BacrGameCore core = entry.getValue();
			if (core.getCurStatus() == BacrGameStatus.IDLE) {
				core.startGame(waitTimeDuration);
			}
		}
	}

	/**
	 * 修正連勝紀錄的時間(活動、排行榜連勝)
	 */
	public void fixCombosInfo(BacrGameCore core) {
		// 先看維護的時候GMTOOL有沒有要更新活動
		if (core.getGMToolChangeActivityCombosInfo() != null) {
			core.setActivityCombosInfoFromGMTool(core.getGMToolChangeActivityCombosInfo());
			core.cleanActivityCombosInfoFromGMTool();
		}

		// 處理修正紀錄時間
		if (core.getPlayerComboInfos() != null && core.getPlayerComboInfos().size() > 0) {
			long currentTime = TimeSys.currentTimeMills();
			for (Entry<String, BacrCombosData> entry : core.getPlayerComboInfos().entrySet()) {
				String guid = entry.getKey();
				BacrCombosData data = entry.getValue();

				// 如果還有在活動連勝
				if (data.getActivityCombos() > 0 && data.getLastActivityComboTime() > 0L)
					data.setLastActivityComboTime(currentTime);
				// 如果還有在排行榜連勝
				if (data.getRankCombos() > 0 && data.getLastRankComboTime() > 0L)
					data.setLastRankComboTime(currentTime);

				core.getPlayerComboInfos().put(guid, data);
			}
		}

		// 處理廣播活動連勝訊息(如果活動有開才要再廣播一次，因為百家樂啟動之前都不會告訴玩家活動相關的紀錄)
		if (core.getActivityCombosInfoModel() != null && core.isInActivityCombosDuration())
			core.broadcastAcitvityCombosInfoChange();
	}

	/**
	 * 收到GMTOOL修正活動連勝的設定值
	 */
	public void recvGMToolUpdateActivityCombosInfo(BacrActivityCombosInfoModel model) {
		// 重複檢查一次module是否擁有這桌
		if (!gameCoreMap.containsKey(model.getTableID())) {
			return;
		}
		// 設定進牌桌放著
		BacrGameCore core = gameCoreMap.get(model.getTableID());
		core.setGMToolChangeActivityCombosInfo(model);

		// 如果曾經設定過關閉
		if (!model.isActive() && !core.isGMToolEverChangeToCloseActivityCombos())
			core.setGMToolEverChangeToCloseActivityCombos(true);

		if (log.isInfoEnabled()) {
			log.info("tableID: " + model.getTableID() + " apply GMTool change BacrActivityCombosInfo setting");
		}

	}

	/**
	 * 送更新排行榜<br>
	 * 在這邊才會設定連勝時間
	 */
	public void sendUpdateBacrRankComboToRankingModule(BacrGameCore core) {
		long currentTime = TimeSys.currentTimeMills();
		// 交給排行榜MODULE處理連勝排行榜
		if (core.getPlayerCombosRankUpMap() != null && core.getPlayerCombosRankUpMap().size() > 0) {
			ConcurrentHashMap<GUID, Integer> rankUpMap = core.getPlayerCombosRankUpMap();
			for (Entry<GUID, Integer> entry : rankUpMap.entrySet()) {
				GUID playerId = entry.getKey();
				Integer combos = entry.getValue();
				String playerName = core.getPlayerComboInfos().get(playerId.toString()).getPlayerName();

				// 設定連勝時間
				core.getPlayerComboInfos().get(playerId.toString()).setLastRankComboTime(currentTime);

				if (combos > 1) {
				} // if (combos > 1) {
			} // for(Entry<GUID, Integer> entry : rankUpMap.entrySet()){
			core.clearCombosRankUpMap();
		}
	}

	/**
	 * 送活動連勝資訊給CLIENT<br>
	 * 在這邊才會設定連勝時間(以免傳給CLIENT是下注計算的時間)
	 */
	public void sendUpdateBacrActivityCombosToClient(BacrGameCore core) {
		long currentTime = TimeSys.currentTimeMills();
		if (core.getActivityCombosScoreInfos().size() > 0) {
			HashMap<GUID, BacrPlayerActivityCombosInfo> infos = core.getActivityCombosScoreInfos();
			for (Entry<GUID, BacrPlayerActivityCombosInfo> entry : infos.entrySet()) {
				BacrPlayerActivityCombosInfo info = entry.getValue();
				GUID playerGUID = entry.getKey();
				// 在這邊才會設定連勝時間
				if (info.getActivityCombos() > 0) {
					core.getPlayerComboInfos().get(playerGUID.toString()).setLastActivityComboTime(currentTime);
				}
				// 設定給CLIENT看的連勝資訊
				core.setBacrActivityCombosInfoForClient(playerGUID.toString());

				Player player = core.getPlayerInTable(playerGUID);
				// 玩家還在桌內的話
				if (player != null) {
					int activityCombos = info.getActivityCombos();
					int nextActivityCombos = info.getNextActivityCombos();
					long activityCombosScore = info.getActivityCombosScore();
					long nextActivityCombosScore = info.getNextActivityCombosScore();
					long experationTime = core.getPlayerComboInfos().get(playerGUID.toString())
							.getExperationTime();
					int firstStageCombos = core.getActivityCombosInfoModel().getComboStage1();
					long firstStageCombosScore = core.getActivityCombosInfoModel().getScoreStage1();

					SBacrActivityCombosData msg = new SBacrActivityCombosData();
					msg.setActivityCombos(activityCombos);
					msg.setNextActivityCombos(nextActivityCombos);
					msg.setActivityCombosScore(activityCombosScore);
					msg.setNextActivityCombosScore(nextActivityCombosScore);
					msg.setExperationTime(experationTime);
					msg.setLawestLimitBet(core.getActivityCombosInfoModel().getLowestBet());
					msg.setFirstStageCombos(firstStageCombos);
					msg.setFirstStageCombosScore(firstStageCombosScore);

					// 送消息
					MessageAction.sendMessage(player.getSessionID(), MessageTypes.S_BACR_UPDATE_ACTIVITY_COMBOS,
							msg);

				}
			}
		}
		// 處理完順便CLEAR一下暫存MAP
		core.clearActivityCombosLogsMap();
	}

	/**
	 * 定時處理過期的COMBO紀錄<br>
	 * 先給牌桌設定一個TIMER來過的標記<br>
	 * 牌桌自己在可以清的階段處理清除
	 */
	public void onTimerTriggerCleanCombosMark() {
		for (BacrGameCore core : gameCoreMap.values()) {

			// 如果已經給過標記或是還沒收到開機的指令就換下一個牌桌看看
			if (core.isCleanCombosTimerTick() || core.getCurStatus() == BacrGameStatus.IDLE) {
				continue;
			} else {
				core.setCleanCombosTimerTick(true);
			}

			// 看看這桌有沒有儲存的過期紀錄，有就在這時候寫
			if (core.getTimeOutActivityCombsLogs() != null) {
				ArrayList<BacrActivityCombosLogModel> logs = core.getTimeOutActivityCombsLogs();
				for (BacrActivityCombosLogModel model : logs) {
					model.setGameTime(TimeSys.currentTimeMills());// gameTime表示寫DB的時間點(eventTime是暫存被製造的時間點)
					getOwnerModule().saveDbLog(model);
				}
				// 清空暫存的過期紀錄
				core.getTimeOutActivityCombsLogs().clear();
			}

		}
	}

	/**
	 * 1. 先把上次關機存的刪掉<br>
	 * 2. 再把這次關機連勝相關的資訊存在REDIS，等開機之後再LOAD進來<br>
	 * 包含 活動連勝 & 排行榜連勝
	 */
	public void saveCombosInfoForReboot(BacrGameCore core) {
		// 以桌號區別
		String key = GameLogDAO.COMBOS_RECORD_FOR_REBOOT + core.getTableId();
		// 存玩家GUID
		String field = "";
		// 存玩家保留的連勝狀態
		String value = "";

		Gson gson = new Gson();
		ConcurrentHashMap<String, BacrCombosData> savingData = core.getPlayerComboInfos();

		// 先刪除上次的
		BacrSaveNewCombosTempToRedisAsync async = new BacrSaveNewCombosTempToRedisAsync();
		async.setDeleteOnly(true);
		async.setKey(key);
		getOwnerModule().addAsyncCommand(async);

		if (savingData != null) {
			for (Entry<String, BacrCombosData> entry : savingData.entrySet()) {
				BacrCombosData data = entry.getValue();
				// 如果活動連勝過期了就先寫紀錄&歸零
				if (!data.isActivityTimeEffect() && data.getActivityCombos() > 0) {
					BacrActivityCombosLogModel logModel = new BacrActivityCombosLogModel();
					logModel.setActivityCombos(data.getActivityCombos());
					logModel.setActivityCombosScore(0L);
					logModel.setLogType(BacrActivityCombosLogModel.LOG_TYPE_OUT_OF_EXPERATION_TIME_WHEN_SHUT_DOWN);
					logModel.setRoundNumber(core.getRoundNumber());
					getOwnerModule().saveDbLog(logModel);

					data.setActivityCombos(0);
					data.setActivityCombos_ForClient(0);
					data.setLastActivityComboTime(0L);
				}
				// 如果排行榜連勝過期了就先歸零
				if (!data.isRankTimeEffect() && data.getRankCombos() > 0) {
					data.setRankCombos(0);
					data.setLastRankComboTime(0L);
				}

				field = entry.getKey();
				value = gson.toJson(data);

				BacrSaveNewCombosTempToRedisAsync SaveAsync = new BacrSaveNewCombosTempToRedisAsync();
				SaveAsync.setDeleteOnly(false);
				SaveAsync.setKey(key);
				SaveAsync.setField(field);
				SaveAsync.setValue(value);
				getOwnerModule().addAsyncCommand(SaveAsync);
			}
		}

	}

	@Override
	public void revClientStopAutoSpinOptionQuery() {
	}

	// -------------------↓新框架以下函式己不使用↓--------------------------------------
	
	@Override
	public void revClientGameSeatVerify(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revClientGameSeatVerifyBack(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revGameLeaveSeat(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revGameLeaveSeatBack(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revClientSpin(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revGameJackPotLockBack(SGameJackPotLockResponse request) throws NoSuchMethodException,
			SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// TODO Auto-generated method stub
	}

	@Override
	public void revGameHistoryRecord(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revSeatPlayerInfoRequest(GUID sessionId, SSlotCommonDmSeatPlayerInfoRequest request) {
		// TODO Auto-generated method stub
	}

	@Override
	public void revSeatVerifyInfoRequest(SlotBasicGameMessageRequest request) {
		// TODO Auto-generated method stub
	}
}
